/*
 *  Copyright 2005 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.persistence.hibernate.questionnaire;

import com.blandware.atleap.common.Constants;
import com.blandware.atleap.common.parsers.exception.PlainTextExtractorException;
import com.blandware.atleap.common.parsers.html.HTMLPlainTextExtractor;
import com.blandware.atleap.common.util.ConvertUtil;
import com.blandware.atleap.common.util.PartialCollection;
import com.blandware.atleap.common.util.QueryInfo;
import com.blandware.atleap.model.core.*;
import com.blandware.atleap.model.questionnaire.Questionnaire;
import com.blandware.atleap.model.questionnaire.Answer;
import com.blandware.atleap.model.questionnaire.AnswerOption;
import com.blandware.atleap.model.questionnaire.FilledQuestionnaire;
import com.blandware.atleap.persistence.exception.DeleteException;
import com.blandware.atleap.persistence.hibernate.core.PageDAOHibernate;
import com.blandware.atleap.persistence.questionnaire.QuestionnaireDAO;

import java.io.ByteArrayInputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;

/**
 * <p>Hibernate implementation of QuestionnarieDAO</p>
 * <p><a href="QuestionnaireDAOHibernate.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.4 $ $Date: 2008/06/23 13:24:55 $
 */
public class QuestionnaireDAOHibernate extends PageDAOHibernate
        implements QuestionnaireDAO {

    // ~ CRUD methods ================================================================

	/**
	 * Creates new instance of QuestionnaireDAOHibernate
	 */
	public QuestionnaireDAOHibernate() {
	}

	/**
	 * @see com.blandware.atleap.persistence.questionnaire.QuestionnaireDAO#createQuestionnaire(com.blandware.atleap.model.questionnaire.Questionnaire, java.util.Map)
	 */
	public Long createQuestionnaire(Questionnaire questionnaire, Map linkedObjects) {
		questionnaire.setUsageCounter(new Integer(0));
        questionnaire.setLastUpdatedDatetime(new Date());
        questionnaire.setActive(Boolean.FALSE);
        questionnaire.setFixed(Boolean.FALSE);
		Long questionnaireId = (Long) getHibernateTemplate().save(questionnaire);

		// title
		ContentField fTitle = new ContentField();
		fTitle.setIdentifier("title");
		fTitle.setType(ContentField.LINE_TYPE);
		fTitle.setInternal(Boolean.TRUE);
		questionnaire.addContentField(fTitle);
		getHibernateTemplate().save(fTitle);

		// annotation
		ContentField fAnnotation = new ContentField();
		fAnnotation.setIdentifier("annotation");
		fAnnotation.setType(ContentField.HTML_TYPE);
		fAnnotation.setInternal(Boolean.TRUE);
		questionnaire.addContentField(fAnnotation);
		getHibernateTemplate().save(fAnnotation);

        // Fill in content field values for title and annotation
        // fields for every content locale
		List contentLocales = executeFind("from ContentLocale l", true, "query.listContentLocales");
		HTMLPlainTextExtractor extractor = new HTMLPlainTextExtractor();
		for ( int i = 0; i < contentLocales.size(); i++ ) {
			ContentLocale contentLocale = (ContentLocale) contentLocales.get(i);
			String localeIdentifier = contentLocale.getIdentifier();

            // values for title and annotation in that locale
			String valueTitle = (String) questionnaire.getTitle().get(localeIdentifier);
			String valueAnnotation = (String) questionnaire.getAnnotation().get(localeIdentifier);

            // skip a locale that all values for it are empty
			if ( (valueTitle == null || valueTitle.trim().length() == 0) && (valueAnnotation == null || valueAnnotation.trim().length() == 0) ) {
				continue;
			}

			//title
			ContentFieldValue fvTitle = new ContentFieldValue();
			fvTitle.setContentLocale(contentLocale);
			if ( valueTitle != null ) {
				fvTitle.setSimpleValue(valueTitle);
			} else {
				fvTitle.setSimpleValue("");
			}
			fvTitle.setLastUpdatedDatetime(new Date());
			fTitle.addContentFieldValue(fvTitle);
			getHibernateTemplate().save(fvTitle);

			//annotation
			ContentFieldValue fvAnnotation = new ContentFieldValue();
			fvAnnotation.setContentLocale(contentLocale);
			if ( valueAnnotation != null ) {
				byte[] annotationBytes = ConvertUtil.convertToByteArray(valueAnnotation);
				fvAnnotation.setValue(annotationBytes);
				// get linked objects
				try {
					Set refs = new HashSet(extractor.extractAllRefs(new ByteArrayInputStream(annotationBytes), Constants.DEFAULT_ENCODING));
					List linkedPages = new ArrayList();
					List linkedResources = new ArrayList();
					for ( Iterator j = refs.iterator(); j.hasNext(); ) {
						String ref = (String) j.next();
						BaseObject linkedObject = (BaseObject) linkedObjects.get(ref);
						if ( linkedObject != null ) {
							if ( linkedObject instanceof ContentResource ) {
								linkedResources.add(linkedObject);
							} else if ( linkedObject instanceof Page ) {
								linkedPages.add(linkedObject);
							}
						}
					}
					fvAnnotation.setLinkedResources(linkedResources);
					fvAnnotation.setLinkedPages(linkedPages);
				} catch ( PlainTextExtractorException e ) {
					// log exception
					if ( log.isErrorEnabled() ) {
						StringWriter sw = new StringWriter();
						e.printStackTrace(new PrintWriter(sw));
						log.error(sw.toString());
					}
				}
			} else {
				fvAnnotation.setValue(new byte[0]);
			}
			fvAnnotation.setLastUpdatedDatetime(new Date());
			fAnnotation.addContentFieldValue(fvAnnotation);
			getHibernateTemplate().save(fvAnnotation);
		}

		return questionnaireId;
	}

	/**
	 * @see com.blandware.atleap.persistence.questionnaire.QuestionnaireDAO#retrieveQuestionnaire(Long)
	 */
	public Questionnaire retrieveQuestionnaire(Long questionnaireId) {
		String hql = new StringBuffer("select questionnaire, title.contentLocale.identifier, title.simpleValue, annotation.value ")
		        .append("from Questionnaire as questionnaire left outer join ")
		        .append("questionnaire.contentFields titleField left outer join titleField.contentFieldValues as title left outer join ")
		        .append("questionnaire.contentFields annotationField left outer join annotationField.contentFieldValues as annotation ")
		        .append("where ")
		        .append("((title.contentLocale = annotation.contentLocale) or (title is null and annotation is null)) ")
		        .append("and titleField.identifier = 'title' ")
		        .append("and annotationField.identifier = 'annotation' and questionnaire.id = ?")
		        .toString();

        // Get a list of quintuples (questionnaire, locale identifier, title value,
        // annitation value, body value)
		List list = executeFind(hql, new Object[]{questionnaireId});
        // Assign those values to corresponding questionnaires -- get list of items with
        // title, annotation and body correctly assigned
		List result = setLocalizableFields(list);
		if ( result == null || result.size() <= 0 ) {
			return null;
		} else {
			return (Questionnaire) result.get(0);
		}
	}

	/**
	 * @see com.blandware.atleap.persistence.questionnaire.QuestionnaireDAO#updateQuestionnaire(com.blandware.atleap.model.questionnaire.Questionnaire, java.util.Map)
	 */
	public void updateQuestionnaire(Questionnaire questionnaire, Map linkedObjects) {
		getHibernateTemplate().update(questionnaire);

		HTMLPlainTextExtractor extractor = new HTMLPlainTextExtractor();

        // Update internal fields (title and annotation) of this questionnaire
		for ( int i = 0; i < questionnaire.getContentFields().size(); i++ ) {
			ContentField contentField = (ContentField) questionnaire.getContentFields().get(i);
			Map values;
			boolean blob = true;
			if ( "title".equalsIgnoreCase(contentField.getIdentifier()) ) {
				values = questionnaire.getTitle();
				blob = false;
			} else if ( "annotation".equalsIgnoreCase(contentField.getIdentifier()) ) {
				values = questionnaire.getAnnotation();
			} else {
				continue;
			}
			for ( int j = 0; j < contentField.getContentFieldValues().size(); j++ ) {
				ContentFieldValue contentFieldValue = (ContentFieldValue) contentField.getContentFieldValues().get(j);
				String localeIdentifier = contentFieldValue.getContentLocale().getIdentifier();
				String value = (String) values.get(localeIdentifier);
				if ( value != null ) {
					if ( !blob ) {
						contentFieldValue.setSimpleValue(value);
					} else {
						byte[] valueBytes = ConvertUtil.convertToByteArray(value);
						contentFieldValue.setValue(valueBytes);
						if ( linkedObjects != null ) {
							// get linked objects
							try {
								Set refs = new HashSet(extractor.extractAllRefs(new ByteArrayInputStream(valueBytes), Constants.DEFAULT_ENCODING));
								List linkedPages = new ArrayList();
								List linkedResources = new ArrayList();
								for ( Iterator k = refs.iterator(); k.hasNext(); ) {
									String ref = (String) k.next();
									BaseObject linkedObject = (BaseObject) linkedObjects.get(ref);
									if ( linkedObject != null ) {
										if ( linkedObject instanceof ContentResource ) {
											linkedResources.add(linkedObject);
										} else if ( linkedObject instanceof Page ) {
											linkedPages.add(linkedObject);
										}
									}
								}
								contentFieldValue.setLinkedResources(linkedResources);
								contentFieldValue.setLinkedPages(linkedPages);
							} catch ( PlainTextExtractorException e ) {
								// log exception
								if ( log.isErrorEnabled() ) {
									StringWriter sw = new StringWriter();
									e.printStackTrace(new PrintWriter(sw));
									log.error(sw.toString());
								}
							}
						}
					}
				} else {
					if ( !blob ) {
						contentFieldValue.setSimpleValue("");
					} else {
						contentFieldValue.setValue(new byte[0]);
					}
				}
				contentFieldValue.setLastUpdatedDatetime(new Date());
				contentFieldValue.getContentField().updateContentFieldValue(contentFieldValue);
				getHibernateTemplate().update(contentFieldValue);
			}
		}
	}

    /**
     * @see com.blandware.atleap.persistence.questionnaire.QuestionnaireDAO#updateQuestionnaire(com.blandware.atleap.model.questionnaire.Questionnaire)
     */
    public void updateQuestionnaire(Questionnaire questionnaire) {
        updateQuestionnaire(questionnaire, null);
    }

    /**
	 * @see com.blandware.atleap.persistence.questionnaire.QuestionnaireDAO#deleteQuestionnaire(com.blandware.atleap.model.questionnaire.Questionnaire)
	 */
	public void deleteQuestionnaire(Questionnaire questionnaire) throws DeleteException {
        removeAssociations(questionnaire);

        // break relations between answers and options
        List filledQuestionnaires = new ArrayList(questionnaire.getFilledQuestionnaires());
        for (Iterator i = filledQuestionnaires.iterator(); i.hasNext(); ) {
            FilledQuestionnaire filledQuestionnaire = (FilledQuestionnaire) i.next();
            List answers = new ArrayList(filledQuestionnaire.getAnswers());
            for (Iterator j = answers.iterator(); j.hasNext(); ) {
                Answer answer = (Answer) j.next();
                List answerOptions = new ArrayList(answer.getAnswerOptions());
                for (Iterator k = answerOptions.iterator(); k.hasNext(); ) {
                    AnswerOption answerOption = (AnswerOption) k.next();
                    answer.removeAnswerOption(answerOption);
                }
            }
        }

		getHibernateTemplate().delete(questionnaire);
	}

	// ~ Additional methods ================================================================

	/**
	 * @see com.blandware.atleap.persistence.questionnaire.QuestionnaireDAO#listQuestionnaires(com.blandware.atleap.common.util.QueryInfo)
	 */
	public PartialCollection listQuestionnaires(QueryInfo queryInfo) {
		String whereClause = "";
		String orderByClause = "";
		if ( queryInfo != null ) {
			whereClause = queryInfo.getWhereClause();
			orderByClause = queryInfo.getOrderByClause();
		}

		if ( whereClause == null || whereClause.length() == 0 ) {
			whereClause = "";
		}
		if ( orderByClause != null && orderByClause.length() != 0 ) {
			orderByClause = " order by " + orderByClause;
		} else {
			orderByClause = "";
		}

		List list;
		Integer total = null;

		String localeIdentifier = null;
        boolean activeOnly = false;
		if ( queryInfo != null ) {
			if ( queryInfo.getQueryParameters() != null ) {
				localeIdentifier = (String) queryInfo.getQueryParameters().get("localeIdentifier");
                Boolean activeOnlyValue = (Boolean) queryInfo.getQueryParameters().get("activeOnly");
                activeOnly = (activeOnlyValue != null && activeOnlyValue.booleanValue());
			}
		}

		boolean localeIdentifierPresent = localeIdentifier != null && localeIdentifier.length() > 0;

		String hqlPart;
		ArrayList args = new ArrayList();
		if ( localeIdentifierPresent ) {
			args.add(localeIdentifier);
			hqlPart = new StringBuffer("from Questionnaire as questionnaire ")
			        .append("left outer join questionnaire.contentFields as titleField ")
			        .append("left outer join titleField.contentFieldValues as title ")
			        .append("where ")
			        .append("titleField.identifier = 'title' ")
			        .append("and title.contentLocale.identifier = ? ")
                    .append(activeOnly ? "and questionnaire.active = 'T' " : "")
			        .toString();
			if ( whereClause.length() > 0 ) {
				hqlPart += "and " + whereClause;
			}
		} else {
            if (activeOnly) {
                hqlPart = "from Questionnaire questionnaire where questionnaire.active = 'T' ";
                if ( whereClause.length() > 0 ) {
                    hqlPart += "and " + whereClause;
                }
            } else {
                hqlPart = "from Questionnaire questionnaire ";
                if ( whereClause.length() > 0 ) {
                    hqlPart += "where " + whereClause;
                }
            }
		}

		if ( queryInfo != null && queryInfo.getLimit() != null && queryInfo.getOffset() != null ) {
			// query count
			String hqlForTotal = "select count(distinct questionnaire.id) " + hqlPart;
			total = findUniqueIntegerResult(hqlForTotal, args.toArray());
			if ( total == null ) {
				total = new Integer(0);
			}
		}

        // If we don't have any info about the total number of results yet or
        // we know that there's something that will be found, then fetch data
		if ( total == null || total.intValue() > 0 ) {
			String hql;
			if ( localeIdentifierPresent ) {
				hql = "select distinct questionnaire, title.simpleValue " + hqlPart + orderByClause;
			} else {
				hql = "select distinct questionnaire " + hqlPart + orderByClause;
			}
			list = executeFind(hql, queryInfo, args.toArray());
			if ( total == null ) {
				total = new Integer(list.size());
			}
			if ( localeIdentifierPresent ) {
				for ( ListIterator i = list.listIterator(); i.hasNext(); ) {
					Object[] objects = (Object[]) i.next();
					Questionnaire questionnaire = (Questionnaire) objects[0];
					questionnaire.getTitle().put(localeIdentifier, objects[1]);
					i.set(questionnaire);
				}
			}
		} else {
			list = new ArrayList();
		}


		return new PartialCollection(list, total);

	}

    /**
     * @see com.blandware.atleap.persistence.questionnaire.QuestionnaireDAO#copyQuestionnaire(com.blandware.atleap.model.questionnaire.Questionnaire, java.util.Map)
     */
    public Questionnaire copyQuestionnaire(Questionnaire questionnaire,
                                           Map suffixMap) {
        Questionnaire copy = new Questionnaire();

		copy.setUsageCounter(new Integer(0));
        copy.setLastUpdatedDatetime(new Date());
        copy.setActive(Boolean.FALSE);
        copy.setFixed(Boolean.FALSE);
        copy.setUri(questionnaire.getUri() + "_copy");
		getHibernateTemplate().save(copy);

        // Copy internal fields (title and annotation) of this questionnaire
		for ( int i = 0; i < questionnaire.getContentFields().size(); i++ ) {
			ContentField contentField = (ContentField) questionnaire.getContentFields().get(i);
			Map values;
			boolean blob = true;
			if ( "title".equalsIgnoreCase(contentField.getIdentifier()) ) {
				values = questionnaire.getTitle();
                for (Iterator j = values.keySet().iterator(); j.hasNext(); ) {
                    String key = (String) j.next();
                    String titleValue = (String) values.get(key);
                    if (titleValue == null) {
                        titleValue = "";
                    }
                    String suffix = (String) suffixMap.get(key);
                    if (suffix == null || "".equals(suffix.trim())) {
                        suffix = "_copy";
                    }
                    values.put(key, titleValue + suffix);
                    copy.setTitle(new HashMap(values));
                }
				blob = false;
			} else if ( "annotation".equalsIgnoreCase(contentField.getIdentifier()) ) {
				values = questionnaire.getAnnotation();
                copy.setAnnotation(new HashMap(values));
			} else {
				continue;
			}

            ContentField cfCopy = new ContentField();
            cfCopy.setIdentifier(contentField.getIdentifier());
            cfCopy.setType(contentField.getType());
            cfCopy.setInternal(contentField.getInternal());
            copy.addContentField(cfCopy);
            getHibernateTemplate().save(cfCopy);

			for ( int j = 0; j < contentField.getContentFieldValues().size(); j++ ) {
				ContentFieldValue contentFieldValue = (ContentFieldValue) contentField.getContentFieldValues().get(j);
				String localeIdentifier = contentFieldValue.getContentLocale().getIdentifier();
				String value = (String) values.get(localeIdentifier);

                ContentFieldValue cfvCopy = new ContentFieldValue();
                cfvCopy.setContentLocale(contentFieldValue.getContentLocale());
                cfvCopy.setLastUpdatedDatetime(contentFieldValue.getLastUpdatedDatetime());
                cfCopy.addContentFieldValue(cfvCopy);

				if ( value != null ) {
					if ( !blob ) {
						cfvCopy.setSimpleValue(value);
					} else {
						byte[] valueBytes = ConvertUtil.convertToByteArray(value);
						cfvCopy.setValue(valueBytes);
                        List pages = new ArrayList(contentFieldValue.getLinkedPages());
                        List resources = new ArrayList(contentFieldValue.getLinkedResources());
                        for (Iterator k = pages.iterator(); k.hasNext(); ) {
                            Page page = (Page) k.next();
                            cfvCopy.addLinkedPage(page);
                        }
                        for (Iterator k = resources.iterator(); k.hasNext(); ) {
                            ContentResource resource = (ContentResource) k.next();
                            cfvCopy.addLinkedResource(resource);
                        }
					}
				} else {
					if ( !blob ) {
						cfvCopy.setSimpleValue("");
					} else {
						cfvCopy.setValue(new byte[0]);
					}
				}
				getHibernateTemplate().save(cfvCopy);
			}
		}

		return copy;
    }

	// ~ Finders ================================================================

	/**
	 * @see com.blandware.atleap.persistence.questionnaire.QuestionnaireDAO#findQuestionnaireByUri(String)
	 */
	public Questionnaire findQuestionnaireByUri(String questionnaireUri) {
		return (Questionnaire) findUniqueResult("from Questionnaire questionnaire where questionnaire.uri = ?", new Object[]{questionnaireUri});
	}

    // ~ Helper methods

	/**
	 * Finds questionnaire by id in list.
	 *
	 * @param questionnaires list to search
	 * @param id        id of questionnaire
	 * @return questionnaire
	 */
	protected Questionnaire findQuestionnaireById(List questionnaires, Long id) {
		for ( int i = 0; i < questionnaires.size(); i++ ) {
			Questionnaire questionnaire = (Questionnaire) questionnaires.get(i);
			if ( questionnaire.getId().equals(id) ) {
				return questionnaire;
			}
		}
		return null;
	}

	/**
	 * Collapses list by setting up questionnaires for different locales into
     * one.
	 *
	 * @param queryResult query result with 4 objects (questionnaire, locale,
     * title, annotation)
	 * @return collapsed list
	 */
	protected List setLocalizableFields(List queryResult) {
		if ( queryResult == null || queryResult.size() <= 0 ) {
			return null;
		}

        // First make a list of questionnaires from quadruples (assigning title
        // and annotation values with specified locale)
		List questionnaires = new ArrayList(queryResult.size());
		Set processedLocales = new HashSet();
		for ( int i = 0; i < queryResult.size(); i++ ) {
			Object[] objects = (Object[]) queryResult.get(i);
			Questionnaire questionnaire = (Questionnaire) objects[0];
			String locale = (String) objects[1];
			if ( locale != null && locale.trim().length() > 0 && !processedLocales.contains(locale) ) {
				processedLocales.add(locale);
				questionnaire.getTitle().put(locale, objects[2]);
				questionnaire.getAnnotation().put(locale, ConvertUtil.convertToString((byte[]) objects[3]));
			}

			questionnaires.add(questionnaire);
		}

        // Now make a such list of questionnaires that each item is encountered maximum
        // one time (if some item is encountered second time, fore example, its
        // internal fields' info is added to previous instance)
		List fullQuestionnaires = new ArrayList(questionnaires.size());
		for ( int i = 0; i < questionnaires.size(); i++ ) {
			Questionnaire newItem = (Questionnaire) questionnaires.get(i);
			Questionnaire existingItem = findQuestionnaireById(fullQuestionnaires, newItem.getId());
			if ( existingItem != null ) {
				//title
				Iterator iterator = newItem.getTitle().keySet().iterator();
				while ( iterator.hasNext() ) {
					String localeIdentifier = (String) iterator.next();
					Object value = newItem.getTitle().get(localeIdentifier);
					if ( value == null ) {
						continue;
					}
					existingItem.getTitle().put(localeIdentifier, value);
				}

				//annotation
				iterator = newItem.getAnnotation().keySet().iterator();
				while ( iterator.hasNext() ) {
					String localeIdentifier = (String) iterator.next();
					Object value = newItem.getAnnotation().get(localeIdentifier);
					if ( value == null ) {
						continue;
					}
					existingItem.getAnnotation().put(localeIdentifier, value);
				}

			} else {
				fullQuestionnaires.add(newItem);
			}
		}

		return fullQuestionnaires;
	}
}
